#include "Core/Skybox.h"
#include "Core/Renderer.h"
#include "Core/VBORenderBox.h"
#include "Core/MediaManager.h"
#include "Debug/Logger.h"

#define GL_TEXTURE_CUBE_MAP_ARB             0x8513
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB  0x8515
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB  0x8516
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB  0x8517
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB  0x8518
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB  0x8519
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB  0x851A

namespace blue {

Skybox::Skybox(const char *back, const char *front,
	       const char *bottom, const char *top,
	       const char *right, const char *left) :
  cubemap(NULL), _size(1.0f), _pos(Vector3d(0, 0, 0)), rdrbox(NULL)
{
  create(back, front, bottom, top, right, left);
}

Skybox::~Skybox()
{
  if(cubemap) delete cubemap;
  if(rdrbox) delete rdrbox;
}

void Skybox::load(const char *back, const char *front,
		  const char *bottom, const char *top,
		  const char *right, const char *left) 
{
  if(cubemap) delete cubemap;
  cubemap = NULL;
  if(rdrbox) delete rdrbox;
  rdrbox = NULL;
  create(back, front, bottom, top, right, left);
}

void Skybox::setPosition(const Vector3d &pos)
{
  _pos = pos;
}

void Skybox::setSize(float size)
{
  _size = size;
}

const float Skybox::getSize() const
{
  return _size;
}

const Vector3d &Skybox::getPosition() const
{
  return _pos;
}

void Skybox::draw()
{
  if(rdrbox)
    {
      Renderer *rdr = Renderer::instance();
      
      Shader *tmp = rdr->getShader();
      if(tmp) tmp->setShader(false);

      rdr->pushMatrix();

      glEnable(GL_TEXTURE_CUBE_MAP_ARB); 
      glDepthMask(GL_FALSE);
      glDisable(GL_CULL_FACE);

      if(cubemap) rdr->setTexture(cubemap);

      rdr->translate(_pos);
      rdr->rotate(90, Vector3d(1, 0, 0));
      rdr->scale(Vector3d(_size, _size, _size));

      rdrbox->draw();

      if(cubemap) rdr->setTexture(NULL);

      glEnable(GL_CULL_FACE);
      glDepthMask(GL_TRUE);
      glDisable(GL_TEXTURE_CUBE_MAP_ARB); 

      rdr->popMatrix();

      if(tmp) tmp->setShader(true);
    }
}

void Skybox::create(const char *back, const char *front,
		    const char *bottom, const char *top,
		    const char *right, const char *left)
{
  if(!rdrbox)
    {
      if(Renderer::instance()->checkExtension("GL_ARB_vertex_buffer_object"))
	rdrbox = new VBORenderBox();
    }
  if(rdrbox)
    {
      // load cubemap
      if(Renderer::instance()->checkExtension("GL_ARB_texture_cube_map"))
	{
	  GLenum cube_map_target[6] = {           
	    GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB,
	    GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB,
	    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB,
	    GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB,
	    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB,
	    GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB
	  };

	  Texture *texture_image[6];
	  texture_image[0] = MediaManager::instance()->getMedia<Texture>(left);
	  texture_image[1] = MediaManager::instance()->getMedia<Texture>(right);
	  texture_image[2] = MediaManager::instance()->getMedia<Texture>(bottom);
	  texture_image[3] = MediaManager::instance()->getMedia<Texture>(top);
	  texture_image[4] = MediaManager::instance()->getMedia<Texture>(back);
	  texture_image[5] = MediaManager::instance()->getMedia<Texture>(front);

	  GLuint cube_map_texture_ID;
	  glGenTextures(1, &cube_map_texture_ID);
	  glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, cube_map_texture_ID);

	  for(int i=0; i<6; i++)
	    {
	      glTexImage2D(cube_map_target[i],
			   0,
			   3,
			   texture_image[i]->getSize().X,
			   texture_image[i]->getSize().Y,
			   0,
			   GL_RGB,
			   GL_UNSIGNED_BYTE,
			   texture_image[i]->getData());
	      texture_image[i]->release();
	    }

	  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
	  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);

	  cubemap = new Texture3D(cube_map_texture_ID, back);
	}
      else
	Logger::log() << "Skybox : couldn't load cubemap !" << "\n";

      // vbo data
      float t = 1.0f;
      float cube_pos[72] =   
	{
	  // front face 	
	  -t, -t,  t,
	  t, -t,  t,
	  t,  t,  t,
	  -t,  t,  t,
		
	  // top face 	
	  -t,  t,  t,
	  t,  t,  t,
	  t,  t, -t,
	  -t,  t, -t,
		
	  // back face	
	  t, -t, -t,
	  -t, -t, -t,
	  -t,  t, -t,
	  t,  t, -t,
		
	  // left face	
	  -t, -t, -t,
	  -t, -t,  t,
	  -t,  t,  t,
	  -t,  t, -t,
 
	  // bottom face	
	  -t, -t, -t,
	  t, -t, -t,
	  t, -t,  t,
	  -t, -t,  t,
		
	  // right face		
	  t, -t,  t,
	  t, -t, -t,
	  t,  t, -t,
	  t,  t,  t	
	}; 
 
      int indices[36] =
	{	
	  0, 1, 2, 2, 3, 0,		// front face 
	  4, 5, 6, 6, 7, 4,		// top face 
	  8, 9, 10, 10, 11, 8,	        // back face
	  12, 13, 14, 14, 15, 12,	// left face
	  16, 17, 18, 18, 19, 16,	// bottom face	
	  20, 21, 22, 22, 23, 20	// right face	
	}; 

      rdrbox->initialize(GL_TRIANGLES);
      rdrbox->addBuffer(GL_VERTEX_ARRAY,
			cube_pos,
			72*sizeof(float),
			3);
      rdrbox->addBuffer(GL_TEXTURE_COORD_ARRAY,
			cube_pos,
			72*sizeof(float),
			3);
      rdrbox->addBuffer(GL_INDEX_ARRAY,
			indices, 
			36*sizeof(int),
			1);
    }
  else
    Logger::log() << "Skybox : initialized with no RenderBox !" << "\n";
}

}
